#include<cassert>
#include"fv_mathparser.h"
#include"fv_str_utils.h"
#include"fv_conv_utils.h"
#include"fv_exception.h"

#include<iostream>
#include<sstream>
#include<string>
#include<cmath>
#include<string>


namespace fvmathParser {

	int MathCalculator::GetElement(
		ElemType &type, 
		double &value, 
		std::string &str)
	{
		// if only one character

		// Check sign/operation
		if (str.length() == 1)
		{
			if (str[0] == '^')
			{
				type = Power;
				return 1;
			}
			else if (str[0] == '*')
			{
				type = Multiply;
				return 1;
			}
			else if (str[0] == '/')
			{
				type = Divide;
				return 1;
			}
			else if (str[0] == '+')
			{
				type = Plus;
				return 1;
			}
			else if (str[0] == '-')
			{
				type = Minus;
				return 1;
			}
			else if (str[0] == '(')
			{
				type = Open;
				return 1;
			}
			else if (str[0] == ')')
			{
				type = Close;
				return 1;
			}
		}//if (str.length() == 1)

		// Check if a vector
		if ( (str[0] == 'v') || (str[0] == 'V'))
		{
			try
			{
				std::string str_nr(str.substr(1));
				// Get suffix ex. v0 -> 0
				value = Str2T<double>( str_nr );
				type = Vector;
				return 1;
			}
			catch(...)
			{
				return(-1);
			}
		}

		// Check if a number
		try
		{
			value = (double)Str2T<double>(str);
			type = Number;
			return(1);
		}
		catch(...)
		{
			return(-1);
		}

		// Unrconizable element
		return(-1);
		
	}

	void MathCalculator::GetElement(MathElement& el, std::string &str)
	{
		if (str.length() == 1)
		{
			switch(str[0])
			{
				case '^':
						el.type = Power;
						return;
					break;

				case '*':
						el.type = Multiply;
						return;
					break;

				case '/':
						el.type = Divide;
						return;
					break;
				case '+':
						el.type = Plus;
						return;
					break;
				case '-':
						el.type = Minus;
						return;
					break;
				case '(':
						el.type = Open;
						return;
					break;
				case ')':
						el.type = Close;
						return;
					break;
				//default:
				//    throw CException(-1, "znak nieobslugiwany");
			}//switch(str[0])
		}//if (str.length() == 1)

		if ( (str[0] == 'v') || (str[0] == 'V'))
		{
			try
			{
				//sprawdzenie czy skalda sie tylko z cyfr
				if (str.substr(1).find_first_not_of("0123456789") != std::string::npos)
					throw fv_exception("Error: unable to specify vector's component");
				std::string str_nr(str.substr(1));
				el.value = Str2T<double>(str_nr);
				el.type = Vector;
				return;
				//return 1;
			}
			catch(fv_exception& ex)
			{
				throw ex;
			}
			catch(...)
			{
				//return -1;
				//wyjatek: blad odczytu numeru wektora
				throw fv_exception("Error: unable to specify vector's component");
			}
		}//if ( (str[0] == 'v') || (str[0] == 'V'))

		//14.50

		//jesli pierwszy znakjest + lub -
		//to wykonac dlasza analize
		if ( (str[0] == '+') || (str[0] == '-'))
		{
			//sprawdzenie czy po za pierwszym znakiem dalsze to cyfry pozostala czesc to cyfry
			if (str.substr(1).find_first_not_of("0123456789.") != std::string::npos)
				throw fv_exception("Blad danych elementu - nie mozliwa interpretacja.");
			//sprawdznie czy jest tylko jedna kropka
			try
			{
				el.value = Str2T<double>(str);
				el.type = Number;
				return;
			}
			catch(fv_exception& ex)
			{
				throw ex;
			}
			catch(...)
			{
				throw fv_exception("Error while analizing data - str2double.");
			}

			return;
		}

		if (str.find_first_not_of("0123456789.") != std::string::npos)
		//if (str.find_first_not_of("0123456789.-") != string::npos)
			throw fv_exception("Blad danych elementu - nie mozliwa interpretacja(nie cyfra i nie znak przecinkowy).");

		//sprawdznie czy jest tylko jedna kropka

		try
		{
			el.value = Str2T<double>(str);
			el.type = Number;
			return;
		}
		catch(...)
		{
			throw fv_exception( "Blad analizy danych elementu(strToDouble).");
		}

		//nieznany blad analizy elementu
		throw fv_exception("Nieznany blad analizy elementu.");

	}



	int MathCalculator::ONP(std::string func, unsigned int vectorSize, std::vector<MathElement> &wyjscie)
	{
		std::stack<MathElement> stos;
		//funkcja po dodaniu ewentualnej spacji
		std::string strFunc;

		//stos.clear();
		wyjscie.clear();

		//jesli string jest pusty
		if( func.empty())
			return -1;

		//jesli ostatni element nie jest spacja to ja dodajemy na koncu

		if( func[func.length()-1] != ' ')
		{
			strFunc = func + " ";
		}
		else
			strFunc = func;

		int cntSpaces = countSubString(strFunc, " ");


		int start, stop;
		std::string str;

		//je�li liczba spacji jest mniejsza lub r�wna  zero
		//to sprawdzic czy to jest wektor lub liczba (konewrsja do double) je�li co innego to b�ad

		if ( (cntSpaces < 0) || (cntSpaces == 2) )
			return -1;

		MathElement elTmp;

		// Case - only one expression
		if(cntSpaces == 1)
		{
			MathCalculator::GetElement(elTmp, func);

			// If given element is a number or vector
			if((elTmp.type == Number) || (elTmp.type == Vector))
			{
				wyjscie.push_back(elTmp);
				return(1);
			}
			else
			{
				return(-1);
			}
		}

		// Case - number of operation >= 3

		// Loop over all elements seperated by spaces
		for(int i = 0; i < cntSpaces; ++i)
		{

			start = getSubStrPosition(func, " ", i);
			stop = getSubStrPosition(func, " ", i+1);
			str = func.substr(start+1, stop-start-1);

			//*************************************************************************
			//*************************************************************************
			//  odczytanie elementow
			//*************************************************************************

			//2
			try
			{
				MathCalculator::GetElement(elTmp, str);
				//jesli element jest wektorem to sprawdzic czy skladowa miesci sie w zasiegu
				if (elTmp.type == Vector)
				{
					if ( (elTmp.value < 0) || (  elTmp.value >  vectorSize-1  ) )
						throw fv_exception("Skladowa wektora z po za zakresu.");
				}
			}
			catch(fv_exception& ex)
			{
				//int i = 42;
				std::ostringstream ss;
				ss << "Blad analizy elementu ";
				ss << i;
				ss << " : " << ex;
				throw fv_exception(ss.str() );
			}

			//*************************************************************************
			//*************************************************************************
			//  analiza elementow
			//*************************************************************************

			//test polega na analizie i sprawdzaniu czy na zmiane za podawane
			//wartosci z znakami i koncza sie wartosciami

			//jesli wartosc to wypisa� na wyjscie
			if( (elTmp.type == Number) || (elTmp.type == Vector) )
			{

				//zapisanie na wyjscie
				wyjscie.push_back(elTmp);

			}

			//jesli jeden ze znakow
			//PLUS, MIN, Multiply, Divide, POW
			//to
			if (
				(elTmp.type == Plus) ||
				(elTmp.type == Minus) ||
				(elTmp.type == Multiply) ||
				(elTmp.type == Divide) ||
				(elTmp.type == Power)
				)
			{

				//jesli wektor jest pusty to dodac
				if (stos.size() == 0)
				{
					stos.push(elTmp);
				}
				else//jesli stos nie jest pusty
				{

					//jesli nowy operator jest wyrzszy od aktualnego
					//to zapisujemy element na stos
					if ( MathCalculator::GetPriority(elTmp.type) > MathCalculator::GetPriority(stos.top().type) )
					{
						stos.push(elTmp);
					}
					else
					{
						//jesli nowy operator jest mniejszy lub rwony to
						//przenosimy na wyjscie wszytskie o priorytecie r�wnym lub wyrzszym
						//az do uzyskania priorytetu nizszego
						//badany element zapisac na stos

						//petla z warunkiem
						////while (mathCalculator::GetPriority(elTmp.type) >= mathCalculator::GetPriority(stos.top().type))
						//while (mathCalculator::GetPriority(elTmp.type) < mathCalculator::GetPriority(stos.top().type))
						while (MathCalculator::GetPriority(elTmp.type) <= MathCalculator::GetPriority(stos.top().type))
						{
							wyjscie.push_back(stos.top());
							stos.pop();
							if (stos.size() == 0)
								break;
						}
						stos.push(elTmp);
					}



				}

				//jesli nawias otwierajacy
				//to zapisac go na stos

				//jesli nawias zamykajacy to zdejmowec ze stosu
				//i zapisywac na wyjscie az do napotkania na otwierajacy
			}


			//jesli nawias otwieraj�cy to
			if ( elTmp.type == Open )
			{
				stos.push(elTmp);
			}

			//jesli nawias zamykajacy
			if ( elTmp.type == Close )
			{

				while (stos.top().type != Open)
				{
					wyjscie.push_back(stos.top());
					stos.pop();
				}
				stos.pop();

			}

			#ifdef __WXDEBUG__

			std::cout << std::endl;

			std::cout << "wyjscie: " << mathCalculator::GetString(wyjscie, stos) << std::endl;

			std::cout << std::endl;

			#endif



			//sprawdzenie elementu czym jest
			//i stworzeniem odpowiedniego elementu
			//
			//znakiem: ^, *, /, +, -
			//nawiazem: (, )
			//liczba
			//

			#ifdef __WXDEBUG__
			std::cout << std::endl;
			#endif

		}//for (int i = 0; i < liczbaSpacji; ++i)

		//po zakonczeniu analizy
		//przepisanie wszystkich elementow z stosu na wyjscie
		while (!stos.empty())
		{
			wyjscie.push_back(stos.top());
			stos.pop();
		}

		//#ifdef __WXDEBUG__
		//    std::cout << "Ostateczne rozwiazanie:" << std::endl;
		//    std::cout << "wyjscie: " << mathCalculator::GetString(wyjscie, stos) << std::endl;
		//    std::cout << std::endl;
		//#endif

		if ( (wyjscie.size() % 2) != 1 )
			throw fv_exception( "Niepoprawna liczba elementow funkcji." );

		return 1;

	}

	std::string MathCalculator::GetString(std::vector<MathElement> &vElement, std::stack<MathElement> &stos)
	{

		std::stringstream oss;

		for(size_t i = 0; i < vElement.size(); ++i)
		{

		//if(vElement[i].type

		//POW
		//Multiply, Divide
		//PLUS, Minus
		//OPEN, CLOSE
		//NUM, VEC

			switch(vElement[i].type)
			{
				case Power:
					oss << "^ ";
					break;

				case Multiply:
					oss << "* ";
					break;

				case Divide:
					oss << "/ ";
					break;

				case Plus:
					oss << "+ ";
					break;

				case Minus:
					oss << "- ";
					break;

				case Number:
					oss << vElement[i].value << " ";
					break;

				case Vector:
					oss << "v" << vElement[i].value << " ";
					break;
			}

		}//for (unsigned int i = 0; i < vElement.size(); ++i)

		oss << "        stos top(" << stos.size() << "): ";

		if (stos.size() == 0)
		{
			oss << "non";
		}
		else
		//for (unsigned int i = 0; i < stos.size(); ++i)
		{

			switch(stos.top().type)
			{
				case Power:
					oss << "^ ";
					break;

				case Multiply:
					oss << "* ";
					break;

				case Divide:
					oss << "/ ";
					break;

				case Plus:
					oss << "+ ";
					break;

				case Minus:
					oss << "- ";
					break;

				case Open:
					oss << "( ";
					break;

				case Close:
					oss << ") ";
					break;
			}
		}//if (stos.size() == 0)

		return oss.str();
	}

	std::string MathCalculator::GetString(std::vector<MathElement> &vElement)
	{

		std::stringstream oss;
		for (size_t i = 0; i < vElement.size(); ++i)
		{
			switch(vElement[i].type)
			{
				case Power:
					oss << "^ ";
				break;

				case Multiply:
					oss << "* ";
				break;

				case Divide:
					oss << "/ ";
				break;

				case Plus:
					oss << "+ ";
				break;

				case Minus:
					oss << "- ";
				break;

				case Number:
					oss << vElement[i].value << " ";
				break;

				case Vector:
					oss << "v" << vElement[i].value << " ";
				break;
			}

		}//for (unsigned int i = 0; i < vElement.size(); ++i)

		return oss.str();

	}

	int MathCalculator::GetPriority(ElemType ElType)
	{

		//NUM, VEC, PLUS, MIN, Multiply, Divide, POW, OPEN, CLOSE

		switch(ElType)
		{
			case Power :
				return 3;
				break;

			case Multiply :
				return 2;
				break;
			case Divide :
				return 2;
				break;

			case Plus :
				return 1;
				break;
			case Minus :
				return 1;
				break;

			default:
				return -1;
				break;
		}

	}

	void MathCalculator::Calculate(MathElement &a, MathElement &b, MathElement &sign, MathElement &result, std::vector<double> v)
	{

		double aa, bb;
		int i;

		//jesli wektor lub wartosc numeryczna

		if ( (a.type == Vector) || (a.type == Number) )
		{
		//czy pozycja wektora jest
		if (a.type == Number)
		{
			aa = a.value;
		}
		//jesli jest wektorem to sprawdzic czy miesci sie w zakresie
		else if (a.type == Vector)
		{
			i = (int)a.value;
			if ( (i < 0) || ( i >  (int)v.size() ) )
			{
				//blad zakresu wektora
				//return -2;
				throw fv_exception(  "Wspolzedna wektora z po za zakresu.");
			}
			else
				aa = v[i];
		}
		}
		else
		//return -4;
		throw fv_exception( "Niepoprawna wspolrzedna dzialania");

		if ( (b.type == Vector) || (b.type == Number) )
		{
		//czy pozycja wektora jest
		if (b.type == Number)
		{
			bb = b.value;
		}
		else if (b.type == Vector)
		{
			i = (int)b.value;
			if ( (i < 0) || ( i > (int)v.size() ) )
			{
				//blad zakresu wektora
				//return -2;
				throw fv_exception( "Wspolzedna wektora z po za zakresu.");
			}
			else
				bb = v[i];
		}
		}
		else
		throw fv_exception( "Niepoprawna wspolrzedna dzialania");
		//return -5;

		//czy znak jest znakiem

		//POW
		//Multiply, Divide
		//PLUS, MIN

		double r;

		if (
		(sign.type == Power) ||
		(sign.type == Multiply) ||
		(sign.type == Divide) ||
		(sign.type == Plus) ||
		(sign.type == Minus)
		)
		{
		//wykonanie obliczenia

		if (sign.type == Power)
		{
			r = pow(aa, bb);

			#ifdef __WXDEBUG__
				std::cout << aa << " ^ " << bb << " = " << r << std::endl;
			#endif
		}
		else if (sign.type == Multiply)
		{
			r = aa * bb;

			#ifdef __WXDEBUG__
				std::cout << aa << " * " << bb << " = " << r << std::endl;
			#endif
		}
		else if (sign.type == Divide)
		{
			r = aa / bb;
			#ifdef __WXDEBUG__
				std::cout << aa << " / " << bb << " = " << r << std::endl;
			#endif
		}
		else if (sign.type == Plus)
		{
			r = aa + bb;
			#ifdef __WXDEBUG__
				std::cout << aa << " + " << bb << " = " << r << std::endl;
			#endif
		}
		else if (sign.type == Minus)
		{
			r = aa - bb;
			#ifdef __WXDEBUG__
				std::cout << aa << " - " << bb << " = " << r << std::endl;
			#endif
		}
		}
		else
		//return -3;
		throw fv_exception( "Niepoprawna operacja dzialania");

		result.type = Number;
		result.value = r;

		return;

	}




	int MathCalculator::ONPCalculate(std::vector<MathElement> &vElement, std::vector<double> v, double &result)

	{

		std::stack<MathElement> stos;

		MathElement a, b, r;

		if(vElement.size() == 0 || vElement.size() == 2)
		{
			throw fv_exception( "Blad wykonania elmentow wejsciowych - 0");
		}

		if(vElement.size() == 1)
		{
			//jesli vec lib liczba to ok
			//w przeciwnym wypaku blad
			if(vElement[0].type == Number)
			{
				result = vElement[0].value;
				return 1;
			}
			else if (vElement[0].type == Vector)
			{
				assert(vElement[0].value >= 0);
				result = v[static_cast<int>(vElement[0].value)];
				return 1;
			}
			else
				throw fv_exception( "Blad wykonania elmentow wejsciowych 2");
		}

		//petla po wszytskich elementach
		for(size_t i = 0; i < vElement.size(); ++i)
		{

			//jesli stala lub vec to zapisac na stos
			if((vElement[i].type == Number) || (vElement[i].type == Vector) )
			{
				#ifdef __WXDEBUG__
					std::cout << "Dodanie elementu na stos" << std::endl;
				#endif

				stos.push(vElement[i]);
			}

			if(
				(vElement[i].type == Power) ||
				(vElement[i].type == Multiply) ||
				(vElement[i].type == Divide) ||
				(vElement[i].type == Plus) ||
				(vElement[i].type == Minus)
				)
			{

				#ifdef __WXDEBUG__
					std::cout << "Wykonanie dzialania: " << std::endl;
				#endif

				b = stos.top();
				stos.pop();

				a = stos.top();
				stos.pop();

				#ifdef __WXDEBUG__
					std::cout << "Wykonanie dzialania 2: " << std::endl;
				#endif

				try
				{
					MathCalculator::Calculate(a, b, vElement[i], r, v);
				}
				catch(fv_exception& ex)
				{
					std::ostringstream os;
					os << ex;
					throw fv_exception("Blad wykonania dialana: " + os.str());
				}

				stos.push(r);

			}

		}//for (unsigned int i = 0; i < vElement.size(); ++i)

		result = stos.top().value;

		return 1;
	}
	

}// end namespace fvmatchParser
